#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>

//fs
#include "fatfs.h"

#include "ymodem_fs.h"
#include "function.h"
#include "device.h"
#include "serial.h"

#define SOH         (0x01)
#define STX         (0x02)
#define EOT         (0x04)
#define ACK         (0x06)
#define NAK         (0x15)
#define CA          (0x18)
#define CTRLZ       (0x1a)
#define CTRLC       (0x03)

#define crc16_ccitt(a,b)     fc_crc16_modemx(0,a,b)

static uint32_t _get_system_tick(void)
{
    return system_get_tick();
}

static void _delay(uint32_t ms)
{
    system_delay_ms(ms);
}

static void _send_byte(SERIAL_t serial, uint8_t b)
{
    serial->write(0, &b, 1, NULL);
}

static uint8_t _get_byte(SERIAL_t serial, uint8_t* b, uint16_t delay)
{
    uint8_t ret = 0;

    *b = 0;
    uint32_t current_tick = _get_system_tick();
    while(_get_system_tick() - current_tick < delay)
    {
        if(serial->readable() > 0)
        {
            serial->read(0,b,1,NULL);
            ret = 1;
            break;
        }
        else
        {
            _delay(10);
        }
    }
    return ret;
}

static uint16_t _get_array(SERIAL_t serial, uint8_t* buf, uint16_t length, uint16_t delay)
{
    uint16_t count = 0;
    uint32_t current_tick = _get_system_tick();
    while(_get_system_tick() - current_tick < delay)
    {
        if(serial->readable() > 0)
        {
            serial->read(0,&buf[count++],1,NULL);
            if(count >= length)
            {
                break;
            }
        }
        else
        {
            _delay(10);
        }
    }
    return count;
}

static void int2str(uint8_t *str, int32_t intnum)
{
    uint32_t i, Div = 1000000000, j = 0, Status = 0;

    for (i = 0; i < 10; i++)
    {
        str[j++] = (intnum / Div) + 48;

        intnum = intnum % Div;
        Div /= 10;
        if ((str[j - 1] == '0') & (Status == 0))
        {
            j = 0;
        }
        else
        {
            Status++;
        }
    }
}

static uint16_t _package_start(YMODEM_FS_FILE* yfile, uint8_t* package, uint16_t size)
{
    uint16_t count = 0;
    memset(package,0x00,size);
    package[count++] = SOH;
    package[count++] = 0x00;
    package[count++] = 0xff;

    char *ptch = strrchr(yfile->path,'/');
    ptch++;
    strncpy((char *)&package[count], ptch, 16);
    count += strlen(ptch);
    package[count++] = '\0';

    int2str(&package[count], yfile->length);
    count += strlen((char *)&package[count]);
    package[count++] = '\0';

    for(int i = count; i < 128+3; i++)
    {
        package[count++] = 0x00;
    }
    uint16_t crc = crc16_ccitt(&package[3],128);
    package[count++] = (crc>>8) & 0xFF;
    package[count++] = crc & 0xFF;

    return 128;
}
#if 0
static uint16_t _package_data(YMODEM_FS_FILE* yfile, uint8_t fnum, uint8_t* package, uint16_t size)
{
    uint16_t count = 0;
    memset(package,0x00,size);

    uint32_t file_length = yfile->length;
    uint32_t file_seek = yfile->seek;
    uint32_t remain = file_length-file_seek;
    uint16_t package_size = (remain>128)?(1024):(128);

    package[count++] = (remain>128)?(STX):(SOH);
    package[count++] = fnum;
    package[count++] = ~fnum;

    uint16_t datalen = (remain>package_size)?(package_size):(remain);
    callback(yfile, &package[count], datalen);
    count += datalen;
    for(int i = datalen; i < package_size; i++)
    {
        package[count++] = CTRLZ;
    }
    uint16_t crc = crc16_ccitt(&package[3],package_size);
    package[count++] = (crc>>8) & 0xFF;
    package[count++] = crc & 0xFF;

    return datalen;
}
#endif
static uint16_t _package_end(uint8_t* package, uint16_t size)
{
    uint16_t count = 0;
    memset(package,0x00,size);
    package[count++] = SOH;
    package[count++] = 0x00;
    package[count++] = 0xff;

    for(int i = 0; i < 128; i++)
    {
        package[count++] = 0x00;
    }
    uint16_t crc = crc16_ccitt(&package[3],128);
    package[count++] = (crc>>8) & 0xFF;
    package[count++] = crc & 0xFF;

    return 128;
}


static uint8_t _check_frame(uint8_t* buf, uint16_t length)
{
    if(buf[1] != (unsigned char)(~buf[2]))
        return 0;

    uint16_t crc1 = crc16_ccitt(&buf[3],length-5);
    uint16_t crc2 = (buf[length-2]<<8) + buf[length-1];

    if(crc1 == crc2)
        return 1;
    return 0;
}

int ymodem_receive_fs(void *dev, YMODEM_FS_FILE* yfile)
{
    int ret = -255;
    uint8_t rb = 0;
    uint8_t revbuf[1040] = {0};

    FRESULT res;
    FIL file;
    uint8_t isfileopen = 0;
    uint32_t fseek = 0;
    uint32_t bw = 0;

    uint8_t blk_count = 0;
    uint8_t eot_count = 0;

    SERIAL_t serial = dev;
    for(int i = 0; i < 32 ;i++)
    {
        _send_byte(serial, 'C');
        if(_get_byte(serial, &rb,1000) > 0)
        {
            if(rb == SOH)
            {
                revbuf[0] = SOH;
                uint16_t revlen = _get_array(serial, &revbuf[1],132,2000);
                if((revlen == 132)&&(_check_frame(revbuf,133) > 0))
                {
                    uint16_t offset = 0;
                    char ptname[16];

                    strncpy(ptname,(char *)&revbuf[3],16);
                    offset = strlen((char *)&revbuf[3]);
                    yfile->length = atoi((char *)&revbuf[3+offset+1]);

                    strcat(yfile->path,ptname);
                    res = f_open(&file,yfile->path,FA_CREATE_NEW|FA_WRITE);
                    if(res != FR_OK)
                    {
                        ret = -7;
                        goto _ERROR_EXIT_R;
                    }
                    isfileopen = 1;
                    _delay(50);
                    _send_byte(serial, ACK);
                    _delay(50);
                    _send_byte(serial, 'C');
                    goto _DATA_TRANSFER;
                }
                else
                {
                    _send_byte(serial, NAK);
                }
            }
            else if(rb == CA)
            {
                ret = -1;
                goto _ERROR_EXIT_R;
            }
            else if(rb == CTRLZ)
            {
                ret = -2;
                goto _ERROR_EXIT_R;
            }
            else if(rb == CTRLC)
            {
                ret = -3;
                goto _ERROR_EXIT_R;
            }
            else
            {
                _delay(100);
                serial->ioctl(CMD_REV_INIT, NULL);
                _send_byte(serial, NAK);
            }
        }
    }
    goto _ERROR_EXIT_R;

_DATA_TRANSFER:
    while(1)
    {
        if(_get_byte(serial, &rb, 1000) > 0)
        {
            blk_count = 0;
            if(rb == STX)
            {
                revbuf[0] = rb;
                uint16_t revlen = _get_array(serial, &revbuf[1],1024+5-1,3000);
                if((revlen == 1024+5-1)&&(_check_frame(revbuf,1024+5) > 0))
                {
                    uint16_t sndlen = ((yfile->length-fseek) > 1024)?(1024):(yfile->length-fseek);
                    // callback(yfile,&revbuf[3],sndlen);
                    f_lseek(&file,fseek);
                    res = f_write(&file,&revbuf[3],sndlen,&bw);
                    if(res != FR_OK)
                    {
                        ret = -8;
                        goto _ERROR_EXIT_R;
                    }
                    fseek = fseek + sndlen;
                    _delay(10);
                    _send_byte(serial, ACK);
                }
                else
                {
                    serial->ioctl(CMD_REV_INIT, NULL);
                    _send_byte(serial, NAK);
                }
            }
            else if(rb == SOH)
            {
                revbuf[0] = rb;
                uint16_t revlen = _get_array(serial, &revbuf[1],128+5-1,2000);
                if((revlen == 132)&&(_check_frame(revbuf,133) > 0))
                {
                    uint16_t sndlen = ((yfile->length-fseek) > 128)?(128):(yfile->length-fseek);
                    // callback(yfile,&revbuf[3],sndlen);
                    f_lseek(&file,fseek);
                    res = f_write(&file,&revbuf[3],sndlen,&bw);
                    if(res != FR_OK)
                    {
                        ret = -8;
                        goto _ERROR_EXIT_R;
                    }
                    fseek = fseek + sndlen;
                    _delay(10);
                    _send_byte(serial, ACK);
                }
                else
                {
                    serial->ioctl(CMD_REV_INIT, NULL);
                    _send_byte(serial, NAK);
                }
            }
            else if(rb == EOT)
            {
                if(eot_count == 0)
                {
                    eot_count++;
                    _send_byte(serial, NAK);
                }
                else if(eot_count == 1)
                {
                    _send_byte(serial, ACK);
                    _delay(50);
                    _send_byte(serial, 'C');
                    goto _END;
                }
            }
            else if(rb == CA)
            {
                ret = -5;
                goto _ERROR_EXIT_R;
            }
            else
            {
                _delay(100);
                serial->ioctl(CMD_REV_INIT, NULL);
                _send_byte(serial, NAK);
            }
        }
        else
        {
            blk_count++;
            if(blk_count > 10)
            {
                ret = -6;
                goto _ERROR_EXIT_R;
            }
        }
    }
_END:
    for(int i = 0; i < 16; i++)
    {
        if(_get_byte(serial, &rb, 1000) > 0)
        {
            if(rb == SOH)
            {
                revbuf[0] = rb;
                uint16_t revlen = _get_array(serial, &revbuf[1],128+5-1,2000);
                if(revlen == 132)
                {
                    _send_byte(serial, ACK);
                    goto _EXIT;
                }
                else
                {
                    _send_byte(serial, NAK);
                }
            }
            else if(rb == CA)
            {
                goto _ERROR_EXIT_R;
            }
            else
            {
                _delay(100);
                serial->ioctl(CMD_REV_INIT, NULL);
                _send_byte(serial, NAK);
            }
        }
        else
        {
            // _send_byte(serial, ACK);
            // _delay(50);
            _send_byte(serial, 'C');
        }
    }
    goto _ERROR_EXIT_R;

_EXIT:
    if(isfileopen == 1)
    {
        f_sync(&file);
        f_close(&file);
    }
    return fseek;
_ERROR_EXIT_R:
    if(isfileopen == 1)
    {
        f_sync(&file);
        f_close(&file);
    }
    return ret;
}

#if 1
int ymodem_transmit_fs(void *dev, YMODEM_FS_FILE* yfile)
{
    int ret = 0;
    int iretry = 0;
    uint8_t rb = 0;
    uint8_t sndbuf[1040] = {0};
    uint8_t packageno = 0;
    uint16_t plen = 0;

    FRESULT res;
    FIL file;
    uint8_t isfileopen = 0;
    uint32_t fseek = 0;
    uint32_t br = 0;

    SERIAL_t serial = dev;
    // send start package
    for(iretry = 0; iretry < 32; iretry++)
    {
        serial->ioctl(CMD_REV_INIT, NULL);
        if(_get_byte(serial, &rb,1000) > 0)
        {
            if((rb == 'C')||(rb == NAK))
            {
                _package_start(yfile,sndbuf,128+5);
                res = f_open(&file,yfile->path,FA_READ);
                if(res != FR_OK)
                {
                    ret = -1;
                    goto _ERROR_EXIT_W;
                }
                isfileopen = 1;
                serial->write(0, sndbuf, 128+5, NULL);
            }
            else if(rb == ACK)
            {
                _delay(100);
                break;
            }
            else if(rb == CA)
            {
                ret = -2;
                goto _ERROR_EXIT_W;
            }
            else if(rb == CTRLZ)
            {
                ret = -3;
                goto _ERROR_EXIT_W;
            }
            else if(rb == CTRLC)
            {
                ret = -4;
                goto _ERROR_EXIT_W;
            }
            else
            {}
        }
    }

    if(iretry == 32)
    {
        ret = -5;
        goto _ERROR_EXIT_W;
    }

    // send data package
    serial->ioctl(CMD_REV_INIT, NULL);
    plen = 0;
    while(1)
    {
        if((rb == ACK)||(rb == NAK))
        {
            if(rb == ACK)
            {
                packageno++;
                fseek = fseek + plen;
            }

            if(fseek >= yfile->length)
            {
                _send_byte(serial, EOT);
            }
            else
            {
                uint16_t count = 0;
                uint32_t remain = yfile->length - fseek;
                uint16_t package_size = (remain>128)?(1024):(128);

                sndbuf[count++] = (remain>128)?(STX):(SOH);
                sndbuf[count++] = packageno;
                sndbuf[count++] = ~packageno;

                uint16_t datalen = (remain>package_size)?(package_size):(remain);

                f_lseek(&file,fseek);
                res = f_read(&file,&sndbuf[count],datalen,&br);
                if(res != FR_OK)
                {
                    ret = -6;
                    goto _ERROR_EXIT_W;
                }

                count += datalen;
                for(int i = datalen; i < package_size; i++)
                {
                    sndbuf[count++] = CTRLZ;
                }
                uint16_t crc = crc16_ccitt(&sndbuf[3],package_size);
                sndbuf[count++] = (crc>>8) & 0xFF;
                sndbuf[count++] = crc & 0xFF;
                plen = datalen;

                serial->write(0, sndbuf, (plen>128)?(1024+5):(128+5), NULL);
            }
        }
        // send end package
        else if(rb == 'C')
        {
            plen = _package_end(sndbuf,128+5);
            serial->write(0, sndbuf, 128+5, NULL);
            break;
        }
        else if(rb == CA)
        {
            break;
        }
        _get_byte(serial, &rb,100);
    }

    while(rb != '\r')
    {
        _get_byte(serial, &rb,1000);
    }

    if(isfileopen == 1)
        f_close(&file);
    return yfile->length;
_ERROR_EXIT_W:
    if(isfileopen == 1)
        f_close(&file);
    return ret;
}
#endif
